﻿/*

   GWCA.NET 0.5.198.0 BETA
	  by fritz300 [zechariah.ben.david@gmail.com]
 
 * This class library is designed to duplicate and provide the
 * same functionality as GWCAonstants.au3 except for the .NET framework.
 
  Copyright (C) fritz300 2010
 * 
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Threading;

namespace Gwca.DotNet
{
    
    public class Win32Api
    {
        #region DLL Exports
        [DllImport("user32.dll")]
        public static extern IntPtr PostMessage(IntPtr hWnd, int iMsg, int iWparam, int iLparam);
        [DllImport("user32.dll")]
        public static extern IntPtr SendMessage(IntPtr hWnd, int iMsg, int iWparam, int iLparam);
        [DllImport("kernel32.dll")]
        public static extern IntPtr OpenProcess(uint dwDesiredAccess, Boolean bInheritHandle, int dwProcessId);
        [DllImport("kernel32.dll")]
        public static extern Boolean CloseHandle(IntPtr hObject);
        [DllImport("kernel32.dll")]
        public static extern Int32 ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [In, Out]Byte[] lpBuffer, uint nSize, uint lpNumberOfBytesRead);
        [DllImport("kernel32.dll")]
        public static extern Boolean WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, Byte[] lpBuffer, uint nSize, uint lpNumberOfBytesWritten);
        #endregion
    }

    public class Gw
    {
        #region GWCA Constants

        public enum Ca : int
        {
            GetCurrentTarget = 0x401, GetMyId, Casting, SkillRecharge, SkillAdrenaline,
			StoreVar, SetLogAndHwnd, GetAgentAndTargetPtr, GetSkillbarSkillId, GetMyMaxHP, GetMyMaxEnergy, GetBuildNumber,
			ChangeMaxZoom, GetLastDialogId, SetEngineHook,
			Attack = 0x410, Move, UseSkill,
			ChangeWeaponSet, ZoneMap, DropGold, GoNpc, GoPlayer, GoSignpost, UseAttackSkill,
			EnterChallenge, OpenChest, SetEventSkillMode, UseSkillbarSkill, PickupItem,
			UseSkillbarAttackSkill, Dialog, ChangeTarget, StatusBot, TargetNearestFoe, TargetNearestAlly,
			TargetNearestItem, StatusDelay, TargetCalledTarget, UseHero1Skill, UseHero2Skill, UseHero3Skill, StatusMiss,
			CancelAction, StatusTab, GetNamePtr, CommandHero1, CommandHero2, CommandHero3, CommandAll, ChangeDistrict,
			Resign, ReturnToOutpost, GoAgent, DonateFaction, SetSkillbarSkill, ChangeSecondProfession, TargetNextPartyMember,
			TargetNextFoe, SkipCinematic, DismissBuff, SendChat,
			GetMapLoading = 0x440, GetMapId, GetPing, GetLoggedIn, GetDead, GetBalthFaction, GetKurzFaction, GetLuxonFaction,
			GetTitleTreasure, GetTitleLucky, GetTitleUnlucky, GetTitleWisdom,
			GetAgentExist = 0x450, GetProfessions, GetPlayerNumber,
			GetHP, GetRotation,
			GetSkill, GetCoords, GetWeaponSpeeds, GetSpiritRange, GetTeamId, GetCombatMode,
			GetModelMode, GetHpPips, GetEffects, GetHex, GetModelAnimation, GetEnergy, GetAgentPtr,
			GetType, GetLevel, GetNameProperties, GetMaxId, GetMyNearestAgent,
			GetMyDistanceToAgent, GetNearestAgentToAgent, GetDistanceFromAgentToAgent, GetNearestAgentToAgentEx,
			GetModelState, GetIsAttacking, GetIsKnockedDown, GetIsMoving, GetIsDead, GetIsCasting,
			GetFirstAgentByPlayerNumber, GetAllegiance, GetNearestEnemyToAgentEx, GetIsAttackedMelee, GetNearestItemToAgentEx,
			GetNearestAgentByPlayerNumber, GetSpeed, GetNearestEnemyToAgentByAllegiance, GetNearestAliveEnemyToAgent,
			GetWeaponType, GetNearestSignpostToAgent, GetNearestNpcToAgentByAllegiance, GetNearestAgentToCoords, GetVars,
			GetNearestNpcToCoords, GetLoginNumber, GetNumberOfAgentsByPlayerNumber, GetNumberOfAliveEnemyAgents, GetNextItem,
			GetTarget, SetAttribute, PlayerHasBuff, Hero1HasBuff, Hero2HasBuff, Hero3HasBuff,
			GetGold = 0x510, GetBagSize, SetBag, GetItemId,
			GetIdKit, IdentifyItem, IdentifyItemById, DepositGold, WithdrawGold, SellItem, SellItemById,
			BuyIdKit, BuySuperiorIdKit, PrepareMoveItem, MoveItem, GetItemInfo,
			UseItem, UseItemById, DropItem, DropItemById,
			AcceptAllItems, GetItemLastModifier, FindItemByModelId, FindEmptySlot, FindGoldItem,
			GetItemPositionByItemId, GetItemPositionByModelId, GetItemPositionByRarity, GetItemModelIdById, GetItemInfoById,
			GetItemLastModifierById, EquipItem, EquipItemById, SalvageItem, GetSalvageKit, BuyItem,
			GetTitleSunspear = 0x550, GetTitleLightbringer, GetTitleVanguard, GetTitleNorn, GetTitleAsura, GetTitleDeldrimor,
			GetTitleNorthMastery, GetTitleDrunkard, GetTitleSweet, GetTitleParty, GetTitleCommander, GetTitleLuxon, GetTitleKurzick,
			AddHero = 0x580, KickHero, SwitchMode, AddNpc, KickNpc, TravelGH, LeaveGH, InitMapLoad, MapIsLoaded,
			GetMapOverlayCoords, GetMapOverlayInfo, GetNearestMapOverlayToCoords, GetPartyInfo, ClearPacketQueue, SetHeroMode,
			QuestCheck, QuestCoords, QuestActive, QuestAbandon, SetTeamSize, AllocMem, FreeMem
        };

        public enum ItemRarity : int
        {
            WHITE = 0x3D, BLUE = 0x3F, PURPLE = 0x42, GOLD = 0x40, GREEN = 0x43
        };

        public enum Bag : int
        {
            BackPack = 1, BeltPouch, Bag1, Bag2, EquipmentPack, UnclaimedItems = 7, Storage1, Storage2,
            Storage3, Storage4, Storage5, Storage6, Storage7, Storage8, StorageAnniversary
        };

        public enum Hero : int
        {
            Norgu = 1, Goren, Tahklora, MasterOfWhispers, AcolyteJin, Koss, Dunkoro, AcolyteSousuke, 
            Melonni, ZhedShadowhoof, GeneralMorgahn, MargridTheSly, Olias = 14, Razah, MOX, Jora = 18,
            PyreFierceshot, Livia = 21, Kahmu, Gwen, Xandra, Vekk, Ogden
        };

        public enum HeroMode : int
        {
            Fight, Guard, Avoid
        }

        public enum Chat : int
        {
            All = 0, Guild, Team, Trade, Alliance, Emote = 14
        };

        public enum Misc : int
        {
            FLAG_RESET = 0x7F800000
        }
        #endregion

        #region GWCA Variables

        public static int[] CbVar = new int[2];
        public static IntPtr Cgui;
        #endregion

        #region GWCA Functions per GWCAConstonants.au3

        //** Converts 32-bit int from callbacks to float
        public static float IntToFloat(int IntPtrValue)
        {
            //** TODO: Rewrite function w/out having to use unsafe context.
            unsafe
            {
                int x = IntPtrValue;
                void* px = &x;
                float* pd = (float*)px;

                return *pd;
            }
        }

        //** Converts float from callbacks to 32-bint int
        public static int FloatToInt(float FloatPtrValue)
        {
            //** TODO: Rewrite function w/out having to use unsafe context.
            unsafe
            {
                float x = FloatPtrValue;
                void* px = &x;
                int* pd = (int*)px;

                return *pd;
            }
        }

        //** Posts message to Gw
        public static void Cmd(Ca Msg)
        {
            Cmd(Msg, 0, 0);
        }

        public static void Cmd(Ca Msg, int Wparam)
        {
           Cmd(Msg, Wparam, 0);
        }

        public static void Cmd(Ca Msg, int Wparam, int Lparam)
        {
            CbVar[0] = 0;
            CbVar[1] = 0;
            Win32Api.PostMessage(GetGwProcessHandle(), (int)Msg, Wparam, Lparam);
        }

        //** Sends message to Gw
        public static void CmdEx(Ca Msg)
        {
            CmdEx(Msg, 0, 0);
        }

        public static void CmdEx(Ca Msg, int Wparam)
        {
            CmdEx(Msg, Wparam, 0);
        }

        public static void CmdEx(Ca Msg, int Wparam, int Lparam)
        {
            CbVar[0] = 0;
            CbVar[1] = 0;
            Win32Api.SendMessage(GetGwProcessHandle(), (int)Msg, Wparam, Lparam);
            Application.DoEvents(); //** TODO: Really need to write a better way to wait on message reply...
        }

        //** Sends message to Gw with callback id to return results
        public static int[] CmdCb(Ca Msg)
        {
            return CmdCb(Msg, 0);
        }

        public static int[] CmdCb(Ca Msg, int Wparam)
        {
            CbVar[0] = 0;
            CbVar[1] = 0;

            Win32Api.SendMessage(GetGwProcessHandle(), (int)Msg, Wparam, (int)Cgui);
            Application.DoEvents(); //** TODO: Really need to write a better way to wait on message reply...
            
            return CbVar;
        }

        //** Move to x and y, overloaded to increase random amount of movement.
        public static void MoveEx(float x, float y)
        {
            MoveEx(x, y, 50);
        }

        public static void MoveEx(float x, float y, int Random)
        {
            System.Random Rand = new Random();

            Cmd(Ca.Move, FloatToInt(x + Convert.ToSingle(Rand.Next(-Random, Random))), FloatToInt(y + Convert.ToSingle(Rand.Next(-Random, Random))));

        }

        public static int GetNearestAgentToCoords(float x, float y)
        {
            CmdEx(Ca.GetNearestAgentToCoords, FloatToInt(x), FloatToInt(y));

            return CmdCb(Ca.GetVars, 0)[1];
        }

        public static int[] GetNearestNpcToCoords(float x, float y)
        {
            CmdEx(Ca.GetNearestNpcToCoords, FloatToInt(x), FloatToInt(y));

            return CmdCb(Ca.GetVars, 0);
        }

        public static int[] GetNearestMapOverlayToCoords(float x, float y)
        {
            CmdEx(Ca.GetMapOverlayCoords, FloatToInt(x), FloatToInt(y));
            
            return CmdCb(Ca.GetVars);
        }

        public static void TravelTo(int MapId)
        {
            GwcaTimer MapTimer = new GwcaTimer();
            int MapLoading, MapIsLoaded;
           

            Cmd(Ca.InitMapLoad);
            Cmd(Ca.ZoneMap, MapId);

            do
            {
                Thread.Sleep(200);
                MapLoading = CmdCb(Ca.GetMapLoading)[0];

                if (MapTimer.Duration > 15000 && MapLoading != 2)
                {
                    Cmd(Ca.ZoneMap, 0, MapId);
                }
                MapIsLoaded = CmdCb(Ca.MapIsLoaded)[0];

            } while (MapLoading != 0 && MapIsLoaded != 1);

            MapTimer.Stop();

        }

        public static void MoveItem(int ItemBag, int ItemSlot, int NewBag, int NewSlot)
        {
            Cmd(Ca.PrepareMoveItem, ItemBag, ItemSlot);
            Thread.Sleep(20);
            Cmd(Ca.MoveItem, NewBag, NewSlot);
        }

        public static void MoveItemById(int ItemId, int NewBag, int NewSlot)
        {
            Cmd(Ca.PrepareMoveItem, ItemId, 0);
            Thread.Sleep(20);
            Cmd(Ca.MoveItem, NewBag, NewSlot);
        }

        //** Pickup items
        public static void PickupItems()
        {
            //** TODO: Need to write function overloads to avoid putting these default values..

            int ItemsToPickup = 12;     // ** Max amount of items it will try to pick up at once.
            int MaxDistance = 1012;

            int ItemsPickedUp = 0;
            int[] ClosestItem;          //** Will hold item id and distance of closest on screen item.

            do
            {
                //** Get information about the nearest item, the item id and distance from character.
                ClosestItem = CmdCb(Ca.GetNearestItemToAgentEx, -2);

                //** If there is no item at all, or if that item is further than MaxDistance, end.
                if (ClosestItem[0] == 0 || IntToFloat(ClosestItem[1]) > MaxDistance)
                {
                    break;
                }
                else
                {
                    //** Send command to pickup item.
                    Cmd(Ca.PickupItem, ClosestItem[0]);

                   do
                   {
                       //** Wait 500ms to check if item has been picked up yet or not.
                       Thread.Sleep(500);

                   } while (CmdCb(Ca.GetAgentExist, ClosestItem[0])[0] != 0);  //** Item has been picked up.
                }
                ItemsPickedUp += 1;
            } while (ItemsPickedUp != ItemsToPickup);
        }

        public static int GetItemId(int Bag, int Slot)
        {
            CmdEx(Ca.SetBag, Bag);

            return CmdCb(Ca.GetItemId, Slot)[0];
        }

        public static int[] GetItemInfo(int Bag, int Slot)
        {
            CmdEx(Ca.SetBag, Bag);

            return CmdCb(Ca.GetItemInfo, Slot);
        }

        //** NOTHING PAST THIS POINT TESTED

        public static int[] GetItemLastModifier(int Bag, int Slot)
        {
            CmdEx(Ca.SetBag, Bag);

            return CmdCb(Ca.GetItemLastModifier, Slot);
        }

        public static void MoveToEx(float x, float y, int Random)
        {
            int SavedMapState, Blocked = 0;
            float[] SavedCoords;

            SavedMapState = CmdCb(Ca.GetMapLoading)[0];

            MoveEx(x, y, Random);

            SavedCoords = GetCoords(-2);

            do
            {
                Thread.Sleep(250);
                if (CmdCb(Ca.GetDead)[0] == 1)
                {
                    return;
                }

                if (SavedMapState != CmdCb(Ca.GetMapLoading)[0])
                {
                    return;
                }

                if (SavedCoords == GetCoords(-2))
                {
                    Blocked += 1;
                    MoveEx(x, y, Random);
                }
            } while (ComputeDistanceEx(SavedCoords[0], SavedCoords[1], x, y) > 250 || Blocked > 20);
        }

        public static int ComputeDistanceEx(float[] Coords1, float[] Coords2)
        {
            return ComputeDistanceEx(Coords1[0], Coords1[1], Coords2[0], Coords2[1]);
        }

        public static int ComputeDistanceEx(float x1, float y1, float x2, float y2)
        {
            return Convert.ToInt32(Math.Sqrt((Math.Pow((y2 - y1), 2) + (Math.Pow((x2 - x1), 2)))));
        }

        public static void UseSkillEx(int SkillSlot)
        {
            UseSkillEx(SkillSlot, 0);
        }

        public static void UseSkillEx(int SkillSlot, int Target)
        {
            GwcaTimer Deadlock = new GwcaTimer();

            Cmd(Ca.UseSkillbarSkill, SkillSlot, Target);

            do
            {
                Thread.Sleep(250);
                if (CmdCb(Ca.GetDead)[0] == 1)
                {
                    break;
                }

                if (CmdCb(Ca.GetSkill, -2)[0] == 0 && Deadlock.Duration > 1000)
                {
                    break;
                }
            } while (CmdCb(Ca.SkillRecharge, SkillSlot)[0] == 0 || Deadlock.Duration < 15000);

            Deadlock.Stop();
        }


        #endregion

        #region GWCA.NET Extra Functions
        /* <summary>
         * These are functions that do not neccessarily have an origin in 
         * the original GWCAConstants.au3 for AutoIt.  Some of these functions
         * are critical to GWCA C# operation, while others are added for easier scripting.
         * </summary> */

        //** Required to locate the Guild Wars instance, returns IntPtr
        public static IntPtr GetGwProcessHandle()
        {
            try
            {
                Process[] Processes = Process.GetProcessesByName("Gw");
                foreach (Process p in Processes)
                {
                    return p.MainWindowHandle;
                }
            }
            catch (Exception e)
            {
                //** TODO: Handle exceptions
            }

            return IntPtr.Zero;
        }

        //** Return's pid of GW instance
        public static int GetGwProcessId()
        {
            try
            {
                Process[] Processes = Process.GetProcessesByName("Gw");
                foreach (Process p in Processes)
                {
                    return p.Id;
                }
            }

            catch (Exception e)
            {
                //** TODO: Handle exceptions
            }

            return -1;
        }

        //** Gets current character name
        public static string GetName(int Agent)
        {
            using(MemoryAccess GwProc = new MemoryAccess(GetGwProcessId()))
            {
                return GwProc.ReadMemory((IntPtr)CmdCb(Ca.GetNamePtr, Agent)[0], 0x20);
            }
        }

        //** Gets current x and y Coordinates
        public static float[] GetCoords(int Agent)
        {
            int[] CurrentCoords = new int[2];
            float[] ReturnCoords = new float[2];
            int i = 0;

            CurrentCoords = CmdCb(Ca.GetCoords, Agent);

            foreach (int x in CurrentCoords)
            {
                ReturnCoords[i] = IntToFloat(x);

                i++;
            }

            return ReturnCoords;
        }

        //** Resigns and automatically returns character to outpost
        public static void Resign()
        {
            Cmd(Ca.Resign, 0, 0);

            do
            {
                Thread.Sleep(500);

                if (CmdCb(Ca.GetDead)[0] > 0)  //** Player is dead
                {
                    Cmd(Ca.ReturnToOutpost);
                    break;
                }
            } while (CmdCb(Ca.GetMapLoading)[0] != 0); //** Map has not loaded
        }

        //** Send chat message to Gw, all chat by default unless channel is specificed (See overload).
        public static void SendChat(String Message)
        {
            SendChat(Message, Chat.All);
        }

        public static void SendChat(String Message, Chat Channel)
        {
            int MemPtr = 0;

            //** Automatically set emote channel if '/' exists @ beginning of string.
           if(Message.IndexOf("/", 0) == 0)
           {
               Channel = Chat.Emote;
               Message = Message.Split('/')[1];
           }

            MemPtr = CmdCb(Ca.AllocMem, (Message.Length + 1) * 2)[1];

            using (MemoryAccess GwProc = new MemoryAccess(Gw.GetGwProcessId()))
            {
               GwProc.WriteMemory((IntPtr)MemPtr, Message, 2);
            }

            Thread.Sleep(50);

            Cmd(Ca.SendChat, (int)Channel, MemPtr);

            CmdCb(Ca.FreeMem, MemPtr);
        }

        #endregion

    }

    #region Other Classes

    public class MemoryAccess : IDisposable
    {
        /* <summary>
         * I've written this class to easily make quick read and writes to the Gw process.
         * By default, GWCA should already do most of what we need.  However, in the event
         * that we still need to access the memory, I've added this class to make things easier on the end-user.
         * </summary> */

        private IntPtr ProcessHandle;
        private uint ProcessId;
        private Byte[] Buffer;
        private bool Disposed;


        public MemoryAccess(int ProcessId)
        {
             this.ProcessId = (uint)ProcessId;
             this.ProcessHandle = Win32Api.OpenProcess(0x38, false, ProcessId);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool Disposing)
        {
            if (!Disposed)
            {
                if (Disposing)
                {
                    if (ProcessHandle != IntPtr.Zero)
                    {
                        Win32Api.CloseHandle(ProcessHandle);
                        ProcessHandle = IntPtr.Zero;
                    }
                }

                Disposed = true;
            }
        }
  
        public String ReadMemory(IntPtr Address, uint BytesToRead)
        {
            this.Buffer = new Byte[BytesToRead+1];
            Win32Api.ReadProcessMemory(this.ProcessHandle, Address, this.Buffer, BytesToRead, 0);
            return System.Text.UnicodeEncoding.Unicode.GetString(this.Buffer);
        }

        public Boolean WriteMemory(IntPtr Address, String Data, int AdditionalBytesToWrite)
        {
            return WriteMemory(Address, System.Text.UnicodeEncoding.Unicode.GetBytes(Data), System.Text.UnicodeEncoding.Unicode.GetBytes(Data).Length + AdditionalBytesToWrite);
        }

        public Boolean WriteMemory(IntPtr Address, Byte[] Data)
        {
            return WriteMemory(Address, Data, Data.Length);
        }

        public Boolean WriteMemory(IntPtr Address, Byte[] Data, int SpecifiedSize)
        {
            this.Buffer = Data;
            return Win32Api.WriteProcessMemory(this.ProcessHandle, Address, this.Buffer, (uint)SpecifiedSize, 0);
        }

        //** TODO: Clean up and remove unneccessary overloads
        //** TODO: Write DLL Injection code.
    }

    public class GwcaTimer : System.Windows.Forms.Timer
    {
        /* <summary>
         * This is a simple class derrived from the default windows timer that
         * has an added member, Duration, so that it can be used similarly to the AutoIt timers.
         * 
         * NEEDS WORK THOUGH
         * 
         * </simmary> */ 
        public GwcaTimer() : base()
        {
            this.Tick += new EventHandler(OnTick);
            this.Interval = 100;
            this.Start();
            
        }

        public void Kill()
        {
            this.Dispose();
        }

       public int Duration;

       public void OnTick(object sender, System.EventArgs e)
       {
           Duration += 100 ;
       }
    }
    #endregion
}
